package de.cau.apo.problems.binpacking;

import java.util.ArrayList;
import java.util.List;

import de.cau.apo.container.BitIndexSet;
import de.cau.apo.exceptions.NotFeasibleException;
import de.cau.apo.problems.AbstractSolution;

/**
 * This class represents a possible solution of the bin packing problem.
 * It contains the used bins and is responsive for checking the feasibility
 * and calculating the objective value.
 * 
 * @author Tobias Schwede
 */
public class BPSolution extends AbstractSolution<BPInstance> {
    
    // ########### properties ###########
    
	/** the list of bins that are actually existing in this solution */
    public final List<Bin> bins;
    
    // ########### initialization ###########
    
    /**
     * Creates a new empty solution for the bin packing problem.
     * @param instance the instance for that this solution shall be created
     */
    public BPSolution(BPInstance instance) {
    	super(instance);
        this.bins = new ArrayList<Bin>();
    }
    
    // ########### getter/setter ###########
    
    /**
     * @return the list of the actually existing bins
     */
    public List<Bin> getBins() {
		return bins;
	}
    
    /**
     * @param index the index of a bin
     * @return the bin that was created at the given index
     */
    public Bin getBin(int index) {
    	Bin bin = bins.get(index);
    	if (bin.getIndex() != index) {
    		for (Bin b : this.bins) {
    			if (b.getIndex() == index) {
    				return b;
    			}
    		}
    	}
    	return bin;
    }
    
    /**
     * add the job to the specified bin
     * @param itemIndex the index of the item
     * @param binIndex the index of the bin
     */
    public void addItem(int itemIndex, int binIndex) {
        this.getBin(binIndex).addItem(this.getInstance().getItem(itemIndex));
    }
    
    /**
     * remove the item from the bin
     * @param itemIndex the index of the item
     * @param binIndex the index of the bin
     */
    public void removeItem(int itemIndex, int binIndex) {
    	this.getBin(binIndex).removeItem(this.getInstance().getItem(itemIndex));
    }
    
    /**
     * create and add a new bin to this solution
     * @return the newly created bin
     */
    public Bin createNewBin() {
    	Bin bin = new Bin(this.bins.size());
    	this.bins.add(bin);
    	return bin;
    }
    
    /**
     * @return the count of actually existing bins
     */
    public int getBinCount() {
    	return this.bins.size();
    }
    
    /**
     * remove all bins
     */
    public void clear() {
        this.bins.clear();;
    }
    
    // ########### AbstractSolution implementation ###########
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void isFeasible() throws NotFeasibleException {
    	BitIndexSet items = new BitIndexSet(getInstance().getNumberItems());
        for (Bin bin : this.bins) {
            int remainingCapacity = getInstance().getBinCapacity() - bin.getSize();
        	if (remainingCapacity < 0) {
        		throw new NotFeasibleException(bin + " exceeds the capacity by "+ (-1*remainingCapacity));
        	}
        	for (Item item : bin.getItems()) {
        		items.add(item.getIndex());
        	}
        }
        if (items.size() != getInstance().getNumberItems()) {
        	throw new NotFeasibleException("Not all items are packed.");
        }
    }
    
    /**
	 * {@inheritDoc}
	 */
	@Override
	public void decode(String solution) {
		this.clear();
		String[] bins = solution.split("#");
		for (String bin : bins) {
			bin = bin.trim();
			Bin binObj = this.createNewBin();
			String[] items = bin.split(" ");
			for (String item : items) {
				item = item.trim();
				binObj.addItem(this.getInstance().getItem(Integer.parseInt(item)));
			}
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String encode() {
		return this.toString();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public double getObjValue() {
        return this.getBinCount();
	}
	
	// ########### overriding toString() ###########

    @Override
    public String toString() {
        String string = "";
        for (Bin bin : this.bins) {
        	// for each bin list the indices of the containing items white-space-separated
        	for (Item item : bin.getItems()) {
        		string += item.getIndex() + " ";
        	}
        	// remove last whitespace character and close the current bin by '#'
        	string = string.trim()+ "#";
        }
        return string;
    }
    
}
